
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "nuc900_reg.h"
#include "wblib.h"
#include "usbd.h"
#include "nuc900_gnand.h"

#ifdef MIO_CACHE

extern UINT8 Mass_LUN;
extern UINT8 F_SM_LUN;

extern NDISK_T *ptMassNDisk;


#define MIO_BLOCK_CNT					16
#define MIO_BLOCK_LEN					64
#define MIO_DIRECT_WRITE_THRESHOLD		640
#define MIO_FLUSH_TIME_THRESHOLD		160

__align(32) UINT8 _MIO_Buff_Pool[MIO_BLOCK_CNT * MIO_BLOCK_LEN * 512];

typedef struct mio_blk_t
{
	INT			lun;
	UINT8		*buff;
	INT			is_dirty;
	UINT32		start_sec;
	UINT32		offset;			/* sector offset from start_sec */
	INT			sec_cnt;		/* in sectors */
	UINT32		time_stamp;
}	MIO_BLK_T;

MIO_BLK_T	_tMioBlk[MIO_BLOCK_CNT];

static UINT32  _uLastAccessTime = 0;


INT  MASS_IO_Init()
{
	INT   i;
	
	memset(&_tMioBlk, 0, sizeof(_tMioBlk));
	
	for (i = 0; i < MIO_BLOCK_CNT; i++)
	{
		_tMioBlk[i].lun    = -1;
		_tMioBlk[i].buff   = (UINT8 *)((UINT32)_MIO_Buff_Pool + i * MIO_BLOCK_LEN * 512 + 0x80000000);
	}
	return 0;
}


void dump(UINT8 lun)
{
	INT  		i;
	MIO_BLK_T	*pmio;

	sysprintf("DUMP ->\n");
	for (i = 0; i < MIO_BLOCK_CNT; i++)
	{
		pmio = &_tMioBlk[i];
		if ((pmio->lun == -1) || (pmio->lun != lun))
			continue;
		sysprintf("   S=%d, O=%d, L=%d\n", pmio->start_sec, pmio->offset, pmio->sec_cnt);
	}
	
}


static INT  find_best_burst(UINT8 lun, INT *start_idx, INT *end_idx, INT *sector_count)
{
	INT   		i, idx, best_idx;
	UINT32		start_sec, sec_cnt, expect_sec;
	MIO_BLK_T	*pmio;
	
	best_idx = -1;
	for (idx = 0; idx < MIO_BLOCK_CNT; idx++)
	{
		if (_tMioBlk[idx].lun != lun)
			continue;

		start_sec  = _tMioBlk[idx].start_sec + _tMioBlk[idx].offset;
		sec_cnt    = _tMioBlk[idx].sec_cnt;
		expect_sec = start_sec + sec_cnt;
		
		for (i = idx+1; i < MIO_BLOCK_CNT; i++)
		{
			pmio = &_tMioBlk[i];

			if ((pmio->lun != lun) || (pmio->start_sec != expect_sec) ||
		    	(pmio->offset != 0) || (pmio->sec_cnt == 0))
				break;
		
			sec_cnt    += pmio->sec_cnt;
			expect_sec += pmio->sec_cnt;
		}
		
		if (best_idx != -1)
		{
			if (sec_cnt < *sector_count)
				continue;						/* not better */
				
			if ((sec_cnt == *sector_count) && 
				(_tMioBlk[idx].time_stamp >= _tMioBlk[best_idx].time_stamp))
			    continue;						/* the same, but not older */
		}

		/* new best choice found */		
		best_idx      = idx;
		*start_idx    = idx;
		*end_idx      = i;
		*sector_count = sec_cnt;
	}
	
	if (best_idx == -1)
		return -1;			/* there's no blocks cached for this lun */
		
	return 0;
}


static INT  mio_flush_cache(UINT8 lun)
{
	INT  		i, start_idx, end_idx, sec_cnt;
	UINT32		start_sec;

	if (find_best_burst(lun, &start_idx, &end_idx, &sec_cnt) < 0)
		return -1;			/* there's no blocks cached for this lun */			
	
	start_sec = _tMioBlk[start_idx].start_sec + _tMioBlk[start_idx].offset;
	
	//sysprintf("    F: %d ~ %d, S=%d, C=%d (%d)\n", start_idx, end_idx-1, start_sec, sec_cnt, sysGetTicks(TIMER0)); 
	
	ptMassNDisk->err_sts = GNAND_write(ptMassNDisk, start_sec, sec_cnt, 
									&(_tMioBlk[start_idx].buff[_tMioBlk[start_idx].offset * 512]));	
	
	for (i = start_idx; i < end_idx; i++)
		_tMioBlk[i].lun = -1;
		
	if (ptMassNDisk->err_sts == 0)
		return start_idx;
	else
		return ptMassNDisk->err_sts;
}


INT  MASS_IO_FLUSH(UINT8 lun)
{
	INT  		status;
	
	//if (sysGetTicks(TIMER0) % 100 == 0)
	//	dump(lun);
	
	if (sysGetTicks(TIMER0) - _uLastAccessTime < MIO_FLUSH_TIME_THRESHOLD)
		return 0;

	status = mio_flush_cache(lun);	
	
	if (status == -1)
		status = 0;			/* cache area is empty */

	return status;
}


INT  MASS_IO_Write(UINT8 lun, UINT32 nSectorNo, INT nSectorCnt, UINT8 *buff)
{
	INT  		i, idx, free_idx;
	UINT32		blk_start_sec;
	INT			offset, write_cnt, end;
	MIO_BLK_T	*pmio;
	
	if (lun != F_SM_LUN)
	{
		sysprintf("MASS_IO_Write - not NAND lun!\n"); 
		return -1;
	}
	
	if (ptMassNDisk->err_sts)
		return ptMassNDisk->err_sts;

	//sysprintf("    W: S=%d, C=%d (%d)\n",  nSectorNo, nSectorCnt, sysGetTicks(TIMER0));

	_uLastAccessTime = sysGetTicks(TIMER0);
	
	if (nSectorCnt >= MIO_DIRECT_WRITE_THRESHOLD)
	{
		return GNAND_write(ptMassNDisk, nSectorNo, nSectorCnt, buff);
	}
	
	while (nSectorCnt > 0)
	{
		offset        = nSectorNo % MIO_BLOCK_LEN;
		blk_start_sec = nSectorNo - offset;
		if (offset + nSectorCnt > MIO_BLOCK_LEN)
			write_cnt = MIO_BLOCK_LEN - offset;
		else
			write_cnt = nSectorCnt;
		
		/*
	 	 * Search IO cache 
	 	 */
		idx = -1;
		free_idx = -1;
		
		for (i = 0; i < MIO_BLOCK_CNT; i++)
		{
			pmio = &_tMioBlk[i];

			if (pmio->lun == -1)
			{
				if (free_idx == -1)
					free_idx = i;
				continue;
			}
			
			if (pmio->lun != lun)
				continue;
		
			if (pmio->start_sec == blk_start_sec)
				idx = i;
		}
		
		if (idx != -1)
		{
			/* 
			 * cache block hit!!
			 */
			pmio = &_tMioBlk[idx];
			
			/*
	 	 	 * Contiguous write? 
	 	 	 */
			if ((offset > pmio->offset + pmio->sec_cnt) ||
			    (pmio->offset > offset + write_cnt))
			{
				/* 
				 * No overlay with current content, read from disk to connect them
				 */
				if (offset > pmio->offset + pmio->sec_cnt)
				{
					ptMassNDisk->err_sts = GNAND_read(ptMassNDisk, pmio->start_sec + pmio->offset + pmio->sec_cnt,
											offset -  (pmio->offset + pmio->sec_cnt),
											&(pmio->buff[(pmio->offset + pmio->sec_cnt) * 512]));
					memcpy(&(pmio->buff[offset * 512]), buff, write_cnt * 512);
					pmio->is_dirty   = 1;
					pmio->sec_cnt    = offset + write_cnt - pmio->offset;
				}
				else
				{
					ptMassNDisk->err_sts = GNAND_read(ptMassNDisk, pmio->start_sec + offset + write_cnt,
											pmio->offset - (offset + write_cnt),
											&(pmio->buff[(offset + write_cnt) * 512]));
					memcpy(&(pmio->buff[offset * 512]), buff, write_cnt * 512);
					pmio->is_dirty   = 1;
					pmio->sec_cnt    = pmio->offset + pmio->sec_cnt - offset;
					pmio->offset     = offset;
				}
			} 
			else
			{
				/*
				 * Overlay with current content
				 */
				memcpy(&(pmio->buff[offset * 512]), buff, write_cnt * 512);
				
				if (offset + write_cnt < pmio->offset + pmio->sec_cnt)
					end = pmio->offset + pmio->sec_cnt;
				else
					end = offset + write_cnt;
				
				if (offset < pmio->offset)
					pmio->offset = offset;
				
				pmio->is_dirty   = 1;
				pmio->sec_cnt    = end - pmio->offset;
			}
		}
		else
		{
			/*
			 * Cache miss, find a free block
			 */
			if (free_idx == -1)
			{
				free_idx = mio_flush_cache(lun);
				if (free_idx < 0)
				{
					sysprintf("MASS_IO_Write - No available slots!\n");
					return free_idx;
				}
			}
			pmio = &_tMioBlk[free_idx];

			pmio->lun = lun;
			memcpy(&(pmio->buff[offset * 512]), buff, write_cnt * 512);
			pmio->is_dirty   = 1;
			pmio->start_sec  = blk_start_sec;
			pmio->offset     = offset;
			pmio->sec_cnt    = write_cnt;
			pmio->time_stamp = sysGetTicks(TIMER0);
		}

		nSectorCnt -= write_cnt;
		nSectorNo  += write_cnt;
		buff       += write_cnt * 512;
	}		
	return 0;
}



INT  MASS_IO_Read(UINT8 lun, UINT32 nSectorNo, INT nSectorCnt, UINT8 *buff)
{
	INT			i;
	UINT32		start, end;
	MIO_BLK_T	*pmio;
	
	if (lun != F_SM_LUN)
	{
		sysprintf("MASS_IO_Read - not NAND lun!\n"); 
		return -1;
	}
	
	if (ptMassNDisk->err_sts)
		return ptMassNDisk->err_sts;

	_uLastAccessTime = sysGetTicks(TIMER0);

	ptMassNDisk->err_sts = GNAND_read(ptMassNDisk, nSectorNo, nSectorCnt, buff);

	for (i = 0; i < MIO_BLOCK_CNT; i++)
	{
		pmio = &_tMioBlk[i];
		
		if ((pmio->lun == -1) || (pmio->lun != lun))
			continue;
			
		start = pmio->start_sec + pmio->offset;
		end   = start + pmio->sec_cnt;
		
		if ((start < nSectorNo + nSectorCnt) && (end >= nSectorNo))
		{
			/*
			 * Overlay with write cache buffers, must update data
			 */
			if (start < nSectorNo)
				start = nSectorNo;
			if (end >= nSectorNo + nSectorCnt)
				end = nSectorNo + nSectorCnt;
				
			memcpy(&(buff[(start - nSectorNo) * 512]), 
					&(pmio->buff[(start - pmio->start_sec) * 512]), (end - start) * 512);
		}
	}
	
	return ptMassNDisk->err_sts;
}

#endif
